package xcmd

import (
	"errors"
	"fmt"
	"net"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
)

// 连接的配置
type SshConfig struct {
	Host     string        // ip
	Port     int           // 端口
	Username string        // 用户名
	Password string        // 密码
	IsPK     bool          // 是否是证书【如果是证书的话，password项填写证书位置/证书字符串】
	Error    error         // 错误信息
	TimeOut  time.Duration // 超时时间
	client   *ssh.Client   // ssh client
}

// 创建连接
func SSH(cliConf *SshConfig) *SshConfig {
	if cliConf == nil {
		return &SshConfig{
			Error: errors.New("服务器配置信息错误"),
		}
	}
	if cliConf.Username == "" {
		cliConf.Error = errors.New("SSH: 登陆账号不允许为空")
		return cliConf
	}
	if cliConf.Password == "" {
		cliConf.Error = errors.New("SSH: 登陆密码不允许为空")
		return cliConf
	}
	if strings.LastIndex(cliConf.Username, "*") > 0 {
		cliConf.Error = errors.New("SSH: 登陆账号不允许存在 *")
		return cliConf
	}
	if cliConf.Port == 0 {
		cliConf.Port = 22
	}
	config := ssh.ClientConfig{
		User: cliConf.Username,
		Auth: []ssh.AuthMethod{ssh.Password(cliConf.Password)},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: 60 * time.Second, // 超时时间设置为60秒
	}
	if cliConf.TimeOut > 0 {
		config.Timeout = cliConf.TimeOut
	}
	if cliConf.IsPK {
		pk, err := publicKeyAuthFunc(cliConf.Password)
		if err != nil {
			cliConf.Error = err
		}
		config.Auth = []ssh.AuthMethod{pk}
	}
	if cliConf.Error != nil {
		return cliConf
	}
	addr := fmt.Sprintf("%s:%d", cliConf.Host, cliConf.Port)
	//获取client
	client, err := ssh.Dial("tcp", addr, &config)
	if err != nil {
		cliConf.Error = err
		return cliConf
	}
	cliConf.client = client
	return cliConf
}

// 运行Shell并获取输出内容
//
//	shell	待运行的命令
func (c *SshConfig) Exec(shell string) (string, error) {
	if c == nil {
		return "", errors.New("服务器配置信息错误")
	}
	if c.Error != nil {
		return "", c.Error
	}
	if c.client == nil {
		return "", errors.New("服务器未连接")
	}
	//获取session，这个session是用来远程执行操作的
	session, err := c.client.NewSession()
	if err != nil {
		return "", err
	}
	// 执行命令
	output, err := session.CombinedOutput(shell)
	if err != nil {
		return "", err
	}
	return string(output), nil
}

// 根据KPATH进行证书签名
//
//	kPath	私钥证书路径
func publicKeyAuthFunc(kPath string) (ssh.AuthMethod, error) {
	if len(kPath) < 1 {
		return nil, errors.New("参数不能为空")
	}
	var key []byte
	if _, err := os.Stat(kPath); err == nil {
		// 私钥证书文件存在，直接进行读取
		key, err = os.ReadFile(kPath)
		if err != nil {
			return nil, fmt.Errorf("SSH密钥文件读取失败：%w", err)
		}
	} else {
		// 证书不存在，即传入的kPath可能为证书的字符串编码
		key = []byte(kPath)
	}
	signer, err := ssh.ParsePrivateKey(key)
	if err != nil {
		return nil, fmt.Errorf("ssh 关键签名失败: %w", err)
	}
	return ssh.PublicKeys(signer), nil
}
